/**
  ******************************************************************************
  * File Name          : SPI.c
  * Description        : This file provides code for the configuration
  *                      of the SPI instances.
  ******************************************************************************
  * This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether 
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * Copyright (c) 2018 STMicroelectronics International N.V. 
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without 
  * modification, are permitted, provided that the following conditions are met:
  *
  * 1. Redistribution of source code must retain the above copyright notice, 
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
  * 3. Neither the name of STMicroelectronics nor the names of other 
  *    contributors to this software may be used to endorse or promote products 
  *    derived from this software without specific written permission.
  * 4. This software, including modifications and/or derivative works of this 
  *    software, must execute solely and exclusively on microcontroller or
  *    microprocessor devices manufactured by or for STMicroelectronics.
  * 5. Redistribution and use of this software other than as permitted under 
  *    this license is void and will automatically terminate your rights under 
  *    this license. 
  *
  * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" 
  * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT 
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
  * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
  * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT 
  * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "bsp/bsp.h"
#include "board.h"
#include "spi.h"
#include "gpio.h"
#include "os/os.h"
#include "bsp/cpu_ticks.h"
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

SPI_HandleTypeDef hspi3;
DMA_HandleTypeDef hdma_spi3_tx;
DMA_HandleTypeDef hdma_spi3_rx;
static volatile bool _finished = false;
static spi_cplt_handler _cplt_callback = NULL;

spi_task_t* task_list_ = NULL;


/* SPI3 init function */
void spi_hw_init(void)
{
  	hspi3.Instance = CONFIG_SPI_DEV;
  	hspi3.Init.Mode = SPI_MODE_MASTER;
  	hspi3.Init.Direction = SPI_DIRECTION_2LINES;
  	hspi3.Init.DataSize = SPI_DATASIZE_16BIT;
  	hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  	hspi3.Init.CLKPhase = SPI_PHASE_2EDGE;
  	hspi3.Init.NSS = SPI_NSS_SOFT;
  	hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  	hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  	hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  	hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  	hspi3.Init.CRCPolynomial = 10;
  	if (HAL_SPI_Init(&hspi3) != HAL_OK){
    
  	}
}

bool equals(const SPI_InitTypeDef* lhs, const SPI_InitTypeDef* rhs) {
  return (lhs->Mode == rhs->Mode)
      && (lhs->Direction == rhs->Direction)
      && (lhs->DataSize == rhs->DataSize)
      && (lhs->CLKPolarity == rhs->CLKPolarity)
      && (lhs->CLKPhase == rhs->CLKPhase)
      && (lhs->NSS == rhs->NSS)
      && (lhs->BaudRatePrescaler == rhs->BaudRatePrescaler)
      && (lhs->FirstBit == rhs->FirstBit)
      && (lhs->TIMode == rhs->TIMode)
      && (lhs->CRCCalculation == rhs->CRCCalculation)
      && (lhs->CRCPolynomial == rhs->CRCPolynomial);
}

bool spi_acquire_task(spi_task_t* task) {
	bool acq = false;
	__disable_irq();
	if (!task->is_in_use) {
		task->is_in_use = true;
		acq = true;
	}
    __enable_irq();
	return acq;
}

void spi_release_task(spi_task_t* task) {
    task->is_in_use = false;
}


static bool spi_start(void) {
    if (!task_list_) {
        return false;
    }

    spi_task_t *task = task_list_;
    if (!equals(task->config, &hspi3.Init)) {
        HAL_SPI_DeInit(&hspi3);
        hspi3.Init = *task->config;
        HAL_SPI_Init(&hspi3);
        __HAL_SPI_ENABLE(&hspi3);
    }

    gpio_write(task->ncs_gpio, false);

    HAL_StatusTypeDef status = HAL_ERROR;

    if (hspi3.hdmatx->State != HAL_DMA_STATE_READY || hspi3.hdmarx->State != HAL_DMA_STATE_READY) {
        // This can happen if the DMA or interrupt priorities are not configured properly.
        status = HAL_BUSY;
    } else if (task->tx_buf && task->rx_buf) {
        status = HAL_SPI_TransmitReceive_DMA(&hspi3, (uint8_t*)task->tx_buf, task->rx_buf, task->length);
    } else if (task->tx_buf) {
        status = HAL_SPI_Transmit_DMA(&hspi3, (uint8_t*)task->tx_buf, task->length);
    } else if (task->rx_buf) {
        status = HAL_SPI_Receive_DMA(&hspi3, task->rx_buf, task->length);
    }

    if (status != HAL_OK) {
        gpio_write(task->ncs_gpio, true);
    }

    return status == HAL_OK;

}


void spi_transfer_task(spi_task_t *task) {
	task->next = NULL;
    // Append new task to task list.
    // We could try to do this lock free but we could also use our time for useful things.
    spi_task_t **ptr = &task_list_;
	u32 mask = cpu_enter_critical();
	while(*ptr) {
		ptr = &((*ptr)->next);
	}
	*ptr = task;
	cpu_exit_critical(mask);
    // If the list was empty before, kick off the SPI arbiter now
    if (ptr == &task_list_) {
        if (!spi_start()) {
            if (task->on_complete) {
                (*task->on_complete)(task->on_complete_ctx, false);
            }
        }
    }
}

static void _temp_complete_cb(void *ctx, bool success) {
	u8 * result = (u8 *)ctx;
	*result = success?1:0;
}

bool spi_transfer(SPI_InitTypeDef *config, u32 ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms) { 
    volatile uint8_t result = 0xff;

    spi_task_t task = {
        .config = config,
        .ncs_gpio = ncs_gpio,
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .length = length,
        .on_complete = _temp_complete_cb,
        .on_complete_ctx = (void*)&result,
        .is_in_use = false,
        .next = NULL
    };

    spi_transfer_task(&task);

	u32 ticks = cpu_ticks_abs();
    while (result == 0xff && cpu_ticks_rel(ticks) < timeout_ms*1000) {
		wdog_reload();
	};
    return (result == 1);

}


void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
    HAL_SPI_TxRxCpltCallback(hspi);
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
    HAL_SPI_TxRxCpltCallback(hspi);
}

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi == &hspi3) {
		if (!task_list_) {
			return;
		}
		spi_task_t *curr = task_list_; //record the head
		gpio_write(curr->ncs_gpio, true);

		// Start next task if any
		spi_task_t* next = NULL;
		__disable_irq();
		next = task_list_ = task_list_->next;
		__enable_irq();

		if (curr->on_complete) {
			curr->on_complete(curr->on_complete_ctx, true);
		}
		if (next) {
			spi_start();
		}
    }
}


void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
  	GPIO_InitTypeDef GPIO_InitStruct;
  	if(spiHandle->Instance==SPI3)
  	{
  		/* USER CODE BEGIN SPI3_MspInit 0 */

  		/* USER CODE END SPI3_MspInit 0 */
    	/* SPI3 clock enable */
    	__HAL_RCC_SPI3_CLK_ENABLE();
  
    	/**SPI3 GPIO Configuration    
    	PC10     ------> SPI3_SCK
    	PC11     ------> SPI3_MISO
    	PC12     ------> SPI3_MOSI 
    	*/
    	gpio_af_init(SPI_SCK_PIO, GPIO_MODE_AF_PP, GPIO_PULLDOWN, GPIO_SPEED_FREQ_MEDIUM, GPIO_AF6_SPI3);
		gpio_af_init(SPI_MOSI_PIO, GPIO_MODE_AF_PP, GPIO_PULLDOWN, GPIO_SPEED_FREQ_MEDIUM, GPIO_AF6_SPI3);
    	// MISO pull-up required for disconnect detection on SPI encoders with even parity
    	gpio_af_init(SPI_MISO_PIO, GPIO_MODE_AF_PP, GPIO_PULLUP, GPIO_SPEED_FREQ_MEDIUM, GPIO_AF6_SPI3);

    	/* SPI3 DMA Init */
    	/* SPI3_TX Init */
    	hdma_spi3_tx.Instance = DMA1_Stream7;
    	hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
    	hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    	hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    	hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;

    	if(spiHandle->Init.DataSize == SPI_DATASIZE_8BIT){
      		hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
      		hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    	} else {
      		hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
      		hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;      
    	}
    
    	hdma_spi3_tx.Init.Mode = DMA_NORMAL;
    	hdma_spi3_tx.Init.Priority = DMA_PRIORITY_HIGH; // SPI TX must have higher priority than SPI RX
    	hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    	if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK)
    	{
      
    	}

    	__HAL_LINKDMA(spiHandle,hdmatx,hdma_spi3_tx);

    	/* SPI3_RX Init */
    	hdma_spi3_rx.Instance = DMA1_Stream0;
    	hdma_spi3_rx.Init.Channel = DMA_CHANNEL_0;
    	hdma_spi3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    	hdma_spi3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    	hdma_spi3_rx.Init.MemInc = DMA_MINC_ENABLE;
    	if (spiHandle->Init.DataSize == SPI_DATASIZE_8BIT) {
        	hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        	hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    	} else {
        	hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        	hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    	}
    	hdma_spi3_rx.Init.Mode = DMA_NORMAL;
    	hdma_spi3_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    	hdma_spi3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    	if (HAL_DMA_Init(&hdma_spi3_rx) != HAL_OK)
    	{
      
    	}
    	__HAL_LINKDMA(spiHandle,hdmarx,hdma_spi3_rx);

  	}
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{

  	if(spiHandle->Instance==SPI3)
  	{
  		/* USER CODE BEGIN SPI3_MspDeInit 0 */

  		/* USER CODE END SPI3_MspDeInit 0 */
    	/* Peripheral clock disable */
    	__HAL_RCC_SPI3_CLK_DISABLE();
  
    	/**SPI3 GPIO Configuration    
    	PC10     ------> SPI3_SCK
    	PC11     ------> SPI3_MISO
    	PC12     ------> SPI3_MOSI 
    	*/
    	gpio_deinit(SPI_SCK_PIO);
		gpio_deinit(SPI_MISO_PIO);
		gpio_deinit(SPI_MOSI_PIO);

    	/* SPI3 DMA DeInit */
    	HAL_DMA_DeInit(spiHandle->hdmatx);
    	HAL_DMA_DeInit(spiHandle->hdmarx);

    	/* SPI3 interrupt Deinit */
    	HAL_NVIC_DisableIRQ(SPI3_IRQn);
  	}
} 

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/**
  * @}
  */

/**
  * @}
  */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
